﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Linq;

namespace Gmantis.Controls
{
    public interface IContextMenu
    {
        // Methods
        void Show(FrameworkElement placementTarget, Point offset);

        // Properties
        FrameworkElement PlacementTarget { get; set; }
    }

    [TemplatePart(Name = "ContextMenuPopup", Type = typeof(Popup)), TemplatePart(Name = "PositioningPanel", Type = typeof(Canvas)), TemplatePart(Name = "MainPanel", Type = typeof(Grid))]
    public class ContextMenu : Menu, IContextMenu
    {
        // Fields
        private MouseHelper _closeMouseHelper;
        internal Popup _elementContextMenuPopup;
        internal Grid _elementMainPanel;
        internal Canvas _elementPositioningPanel;
        private bool _isAutoclosing;
        internal bool _isLoaded;
        private bool _isMousePosDirty;
        private bool _needsInitialization;
        private bool _needUpdatemenuLocation = true;
        private Point _offset;
        private FrameworkElement _root;
        internal const string ContextMenuPopupElementName = "ContextMenuPopup";
        public static readonly DependencyProperty HighlightedBackgroundProperty = DependencyProperty.Register("HighlightedBackground", typeof(Brush), typeof(ContextMenu), null);
        internal const string MainPanelElementName = "MainPanel";
        public static readonly DependencyProperty OffsetProperty = DependencyProperty.Register("Offset", typeof(Point), typeof(ContextMenu), new PropertyMetadata(new PropertyChangedCallback(ContextMenu.OnOffsetPropertyChanged)));
        public static readonly DependencyProperty OpenedBackgroundProperty = DependencyProperty.Register("OpenedBackground", typeof(Brush), typeof(ContextMenu), null);
        public static readonly DependencyProperty PlacementTargetProperty = DependencyProperty.Register("PlacementTarget", typeof(FrameworkElement), typeof(ContextMenu), new PropertyMetadata(new PropertyChangedCallback(ContextMenu.OnPlacementTargetPropertyChanged)));
        internal const string PositioningPanelElementName = "PositioningPanel";

        // Events
        public event EventHandler Closed;

        public event EventHandler<CancelEventArgs> Closing;

        public event EventHandler OffsetChanged;

        public event EventHandler Opened;

        public event EventHandler<CancelEventArgs> Opening;

        // Methods
        public ContextMenu()
        {
            base.DefaultStyleKey = typeof(ContextMenu);
            this.SetCustomDefaultValues();
        }

        private void _closeMouseHelper_ContextMenuInvoked(object sender, MouseEventArgs e)
        {
            if (!this._isMousePosDirty)
            {
                try
                {
                    if (((e.OriginalSource == null) || !(e.OriginalSource is FrameworkElement)) || !(e.OriginalSource as FrameworkElement).GetParents(true).Contains<FrameworkElement>(this._elementPositioningPanel))
                    {
                        if (this._root.HitTestProjectionWorkaround(e.GetPosition(this._root)).Contains<UIElement>(this.PlacementTarget))
                        {
                            Point offset = (e != null) ? e.GetPosition(this.PlacementTarget) : new Point();
                            foreach (MenuItem item in base.MenuItems)
                            {
                                CancelSourceEventArgs args = new CancelSourceEventArgs
                                {
                                    Source = item,
                                    Cancel = false
                                };
                                item.OnSubmenuClosing(args);
                            }
                            base.HighlightedItem = null;
                            this.UpdateMenuLocation(this.PlacementTarget, offset);
                            this.Offset = offset;
                        }
                        else
                        {
                            this.AutoCloseCtxMenu();
                        }
                    }
                }
                catch
                {
                }
            }
            else if (this._elementContextMenuPopup.IsOpen)
            {
                this._isMousePosDirty = false;
            }
        }

        private void _elementPositioningPanel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.AutoCloseCtxMenu();
        }

        private void AttachEvents()
        {
            if (this._root != null)
            {
                this._root.SizeChanged += new SizeChangedEventHandler(this.root_SizeChanged);
            }
            base.SizeChanged += new SizeChangedEventHandler(this.child_SizeChanged);
            this._isMousePosDirty = false;
            this._elementPositioningPanel.MouseLeftButtonDown += new MouseButtonEventHandler(this._elementPositioningPanel_MouseLeftButtonDown);
            this._closeMouseHelper.ContextMenuInvoked += new MouseEventHandler(this._closeMouseHelper_ContextMenuInvoked);
        }

        private void AutoCloseCtxMenu()
        {
            CancelEventArgs e = new CancelEventArgs
            {
                Cancel = false
            };
            this._isAutoclosing = true;
            this.OnAutoClosing(e);
            if (!e.Cancel)
            {
                this.Hide();
                this.OnAutoClosed(new EventArgs());
            }
            this._isAutoclosing = false;
        }

        private void child_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            this.UpdateMenuLocation(this.PlacementTarget, this._offset);
        }

        internal override void CloseMenu()
        {
            CancelEventArgs e = new CancelEventArgs
            {
                Cancel = false
            };
            this.OnClosing(e);
            if (!e.Cancel)
            {
                base.CloseMenu();
                this._isMousePosDirty = true;
                this._elementContextMenuPopup.IsOpen = false;
                this.DetachEvents();
                this.OnClosed(new EventArgs());
            }
        }

        private void DetachEvents()
        {
            if (this._root != null)
            {
                this._root.SizeChanged -= new SizeChangedEventHandler(this.root_SizeChanged);
            }
            base.SizeChanged -= new SizeChangedEventHandler(this.child_SizeChanged);
            this._elementPositioningPanel.MouseLeftButtonDown -= new MouseButtonEventHandler(this._elementPositioningPanel_MouseLeftButtonDown);
            this._closeMouseHelper.ContextMenuInvoked -= new MouseEventHandler(this._closeMouseHelper_ContextMenuInvoked);
        }

        private List<AnchorMatching> GetPreferedLocations()
        {
            List<AnchorMatching> list = new List<AnchorMatching>();
            AnchorMatching item = new AnchorMatching
            {
                FixedElementAnchor = Anchor.BottomLeft,
                MoveableElementAnchor = Anchor.TopLeft
            };
            list.Add(item);
            AnchorMatching matching2 = new AnchorMatching
            {
                FixedElementAnchor = Anchor.BottomRight,
                MoveableElementAnchor = Anchor.TopRight
            };
            list.Add(matching2);
            AnchorMatching matching3 = new AnchorMatching
            {
                FixedElementAnchor = Anchor.TopLeft,
                MoveableElementAnchor = Anchor.BottomLeft
            };
            list.Add(matching3);
            AnchorMatching matching4 = new AnchorMatching
            {
                FixedElementAnchor = Anchor.TopRight,
                MoveableElementAnchor = Anchor.BottomRight
            };
            list.Add(matching4);
            AnchorMatching matching5 = new AnchorMatching
            {
                FixedElementAnchor = Anchor.BottomLeft,
                MoveableElementAnchor = Anchor.TopLeft
            };
            list.Add(matching5);
            return list;
        }

        private T GetTemplateChild<T>(string childName, bool required, ref string errors) where T : class
        {
            DependencyObject templateChild = base.GetTemplateChild(childName);
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "template part", childName, templateChild, required, ref errors);
            return (templateChild as T);
        }

        private static Storyboard GetTemplateChildResource(FrameworkElement root, string resourceName, bool required, ref string errors)
        {
            return GetTemplateChildResource<Storyboard>(root, resourceName, required, ref errors);
        }

        private static T GetTemplateChildResource<T>(FrameworkElement root, string resourceName, bool required, ref string errors) where T : class
        {
            object child = root.Resources[resourceName];
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "resource", resourceName, child, required, ref errors);
            return (child as T);
        }

        public void Hide()
        {
            if (this._isLoaded)
            {
                base.CloseMenu();
                FrameworkElement focusedElement = FocusManager.GetFocusedElement() as FrameworkElement;
                if (base.BelongsToMenu(focusedElement))
                {
                    this.CloseMenu();
                }
                else
                {
                    this._isMousePosDirty = true;
                }
                this._elementContextMenuPopup.IsOpen = false;
                this.DetachEvents();
            }
        }

        private void InitializeContextMenuPopupPart()
        {
            this._elementContextMenuPopup.FlowDirection = FlowDirection.LeftToRight;
            this._elementContextMenuPopup.IsOpen = false;
        }

        private void InitializePositioningPanelPart()
        {
            this._elementPositioningPanel.Background = base.AutoClose ? new SolidColorBrush(Colors.Transparent) : null;
            this._closeMouseHelper = new MouseHelper(this._elementPositioningPanel);
        }

        private bool IsOutOfBorders(Point location, Size size, Size borderSize)
        {
            if (((location.X >= 0.0) && (location.Y >= 0.0)) && ((location.X + size.Width) <= borderSize.Width))
            {
                return ((location.Y + size.Height) > borderSize.Height);
            }
            return true;
        }

        private void OnAfterApplyTemplate()
        {
            if (!PlatformIndependent.IsInDesignMode(this))
            {
                this._root = Application.Current.RootVisual as FrameworkElement;
                this._elementPositioningPanel.Width = this._root.ActualWidth;
                this._elementPositioningPanel.Height = this._root.ActualHeight;
            }
        }

        public override void OnApplyTemplate()
        {
            string errors = string.Empty;
            base.OnApplyTemplate();
            this._isLoaded = true;
            this._elementPositioningPanel = this.GetTemplateChild<Canvas>("PositioningPanel", true, ref errors);
            if (this._elementPositioningPanel != null)
            {
                this.InitializePositioningPanelPart();
            }
            this._elementMainPanel = this.GetTemplateChild<Grid>("MainPanel", true, ref errors);
            this._elementContextMenuPopup = this.GetTemplateChild<Popup>("ContextMenuPopup", true, ref errors);
            if (this._elementContextMenuPopup != null)
            {
                this.InitializeContextMenuPopupPart();
            }
            if (!string.IsNullOrEmpty(errors))
            {
                this._isLoaded = false;
                if ((base.Template != null) && !PlatformIndependent.IsInDesignMode(this))
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Template cannot be applied to ContextMenu.\nDetails: {0}", new object[] { errors }));
                }
            }
            else
            {
                this.OnAfterApplyTemplate();
            }
        }

        protected override void OnAutoCloseChanged(bool oldValue)
        {
            if (this._isLoaded)
            {
                this._elementPositioningPanel.Background = base.AutoClose ? new SolidColorBrush(Colors.Transparent) : null;
            }
        }

        protected virtual void OnClosed(EventArgs e)
        {
            if ((this.Closed != null) && !this._isAutoclosing)
            {
                this.Closed(this, e);
            }
        }

        protected virtual void OnClosing(CancelEventArgs e)
        {
            if ((this.Closing != null) && !this._isAutoclosing)
            {
                this.Closing(this, e);
            }
        }

        internal override void OnGotFocus(object sender, System.Windows.RoutedEventArgs e)
        {
        }

        internal override void OnKeyDown(KeyEventArgs e)
        {
            if ((this._elementContextMenuPopup != null) && this._elementContextMenuPopup.IsOpen)
            {
                base.OnKeyDown(e);
                if ((base.HighlightedItem == null) && base.HasItems)
                {
                    object obj2 = null;
                    if (e.Key == Key.Down)
                    {
                        obj2 = base.Items[0];
                    }
                    else if (e.Key == Key.Up)
                    {
                        obj2 = base.Items[base.Items.Count - 1];
                    }
                    MenuItem container = base.GetContainer(obj2) as MenuItem;
                    if (container != null)
                    {
                        container.IsHighlighted = true;
                    }
                }
            }
        }

        internal override void OnLostFocus(object sender, System.Windows.RoutedEventArgs e)
        {
            FrameworkElement focusedElement = FocusManager.GetFocusedElement() as FrameworkElement;
            if (((focusedElement == null) || !base.BelongsToMenu(focusedElement)) && this._elementContextMenuPopup.IsOpen)
            {
                this.CloseMenu();
            }
        }

        private void OnOffsetChanged(Point previousOffset)
        {
            if ((previousOffset != this.Offset) && (this.OffsetChanged != null))
            {
                this.OffsetChanged(this, new EventArgs());
            }
        }

        private static void OnOffsetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ContextMenu menu = d as ContextMenu;
            Point oldValue = (Point)e.OldValue;
            menu.OnOffsetChanged(oldValue);
        }

        protected virtual void OnOpened(EventArgs e)
        {
            if (this.Opened != null)
            {
                this.Opened(this, e);
            }
        }

        protected virtual void OnOpening(CancelEventArgs e)
        {
            if (this.Opening != null)
            {
                this.Opening(this, e);
            }
        }

        private void OnPlacementTargetChanged(FrameworkElement oldValue)
        {
        }

        private static void OnPlacementTargetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ContextMenu menu = d as ContextMenu;
            FrameworkElement oldValue = (FrameworkElement)e.OldValue;
            menu.OnPlacementTargetChanged(oldValue);
        }

        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);
            MenuItem item2 = element as MenuItem;
            if (item2 != null)
            {
                item2.MenuBehavior = new ContextMenuItemBehavior(item2);
            }
        }

        private void root_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            this.UpdateMenuLocation(this.PlacementTarget, this._offset);
        }

        private void SetCustomDefaultValues()
        {
            this._needsInitialization = true;
            base.LayoutUpdated += delegate(object s, EventArgs e)
            {
                if (this._needUpdatemenuLocation)
                {
                    this._needUpdatemenuLocation = false;
                    this.UpdateMenuLocation(this.PlacementTarget, this._offset);
                }
            };
        }

        public void Show(FrameworkElement placementTarget, Point offset)
        {
            this.PlacementTarget = placementTarget;
            CancelEventArgs e = new CancelEventArgs
            {
                Cancel = false
            };
            this.OnOpening(e);
            if (!e.Cancel)
            {
                if ((base.Parent == null) && (VisualTreeHelper.GetParent(this) == null))
                {
                    FrameworkElement elem = placementTarget ?? this._root;
                    Panel panel = elem as Panel;
                    if (panel == null)
                    {
                        panel = elem.GetAllChildren<Panel>().FirstOrDefault<Panel>();
                    }
                    if (panel == null)
                    {
                        panel = elem.GetAllParents().FirstOrDefault<FrameworkElement>(fe => (fe is Panel)) as Panel;
                    }
                    if (panel != null)
                    {
                        try
                        {
                            panel.Children.Add(this);
                        }
                        catch
                        {
                            panel = null;
                        }
                    }
                    if (panel == null)
                    {
                        Popup popup = new Popup
                        {
                            Child = this,
                            IsOpen = base.Visibility == Visibility.Visible
                        };
                    }
                    base.ApplyTemplate();
                }
                if (this._isLoaded)
                {
                    if (this._needsInitialization)
                    {
                        this._needsInitialization = false;
                    }
                    this._elementContextMenuPopup.IsOpen = base.Visibility == Visibility.Visible;
                    this._elementMainPanel.IsHitTestVisible = base.IsHitTestVisible;
                    this.UpdateMenuLocation(placementTarget, offset);
                    this.AttachEvents();
                    base.Focus();
                    this.OnOpened(new EventArgs());
                }
            }
        }

        private Point TestPosition(Point offset, Size movableElementSize, Anchor movableElementAnchor)
        {
            try
            {
                Point point = new Point();
                if ((movableElementAnchor == Anchor.TopRight) || (movableElementAnchor == Anchor.BottomRight))
                {
                    point.X += double.IsNaN(movableElementSize.Width) ? 0.0 : movableElementSize.Width;
                }
                if ((movableElementAnchor == Anchor.BottomLeft) || (movableElementAnchor == Anchor.BottomRight))
                {
                    point.Y += double.IsNaN(movableElementSize.Height) ? 0.0 : movableElementSize.Height;
                }
                return new Point(offset.X - point.X, offset.Y - point.Y);
            }
            catch (Exception)
            {
                return new Point();
            }
        }

        internal override void UpdateAutoCloseAreas()
        {
        }

        private void UpdateMenuLocation(FrameworkElement placementTarget, Point offset)
        {
            if (this._isLoaded)
            {
                Point point = this.GTransformToVisual(this.GetRootVisual()).Transform(new Point());
                if (base.FlowDirection == FlowDirection.RightToLeft)
                {
                    TranslateTransform transform = new TranslateTransform
                    {
                        X = -point.X + base.ActualWidth,
                        Y = -point.Y
                    };
                    this._elementContextMenuPopup.RenderTransform = transform;
                }
                else
                {
                    TranslateTransform transform2 = new TranslateTransform
                    {
                        X = -point.X,
                        Y = -point.Y
                    };
                    this._elementContextMenuPopup.RenderTransform = transform2;
                }
                FrameworkElement element = placementTarget ?? this._root;
                if (this._root != null)
                {
                    this._elementPositioningPanel.FlowDirection = base.FlowDirection;
                    this._elementPositioningPanel.Width = this._root.ActualWidth;
                    this._elementPositioningPanel.Height = this._root.ActualHeight;
                }
                Point point2 = element.GTransformToVisual(this._root).Transform(new Point());
                Point point3 = new Point(offset.X + point2.X, offset.Y + point2.Y);
                if (this.IsRigthToLeft)
                {
                    point3.X = this._root.ActualWidth - point3.X;
                }
                else if (base.FlowDirection == FlowDirection.RightToLeft)
                {
                    point3.X = (this._root.ActualWidth + offset.X) - point2.X;
                }
                if (base.DetectBoundaries)
                {
                    this.UpdateOffset(ref point3);
                }
                Canvas.SetTop(this._elementMainPanel, point3.Y);
                Canvas.SetLeft(this._elementMainPanel, point3.X);
            }
            this.PlacementTarget = placementTarget;
            this._offset = offset;
        }

        private void UpdateOffset(ref Point offset)
        {
            List<AnchorMatching> preferedLocations = this.GetPreferedLocations();
            Point location = offset;
            int num = 0;
            bool flag = false;
            do
            {
                AnchorMatching matching = preferedLocations[num];
                if (((this._elementMainPanel.DesiredSize.Height == 0.0) && (this._elementMainPanel.DesiredSize.Width == 0.0)) && this._elementContextMenuPopup.IsOpen)
                {
                    this._needUpdatemenuLocation = true;
                }
                location = this.TestPosition(offset, this._elementMainPanel.DesiredSize, matching.MoveableElementAnchor);
                if (this._root != null)
                {
                    flag = this.IsOutOfBorders(location, this._elementMainPanel.DesiredSize, this._root.ActualSize());
                }
                num++;
            }
            while (flag && (num < preferedLocations.Count));
            offset.X = location.X;
            offset.Y = location.Y;
        }

        // Properties
        public Brush HighlightedBackground
        {
            get { return (Brush)GetValue(HighlightedBackgroundProperty); }
            set { SetValue(HighlightedBackgroundProperty, value); }
        }

        internal bool IsOpen
        {
            get
            {
                return ((this._elementContextMenuPopup != null) && this._elementContextMenuPopup.IsOpen);
            }
        }

        private bool IsRigthToLeft
        {
            get
            {
                FrameworkElement parent = base.Parent as FrameworkElement;
                if (parent == null)
                {
                    return (base.FlowDirection == FlowDirection.RightToLeft);
                }
                return ((parent.FlowDirection != FlowDirection.RightToLeft) && (base.FlowDirection == FlowDirection.RightToLeft));
            }
        }

        public Point Offset
        {
            get { return (Point)GetValue(OffsetProperty); }
            set { SetValue(OffsetProperty, value); }
        }

        public Brush OpenedBackground
        {
            get { return (Brush)GetValue(OpenedBackgroundProperty); }
            set { SetValue(OpenedBackgroundProperty, value); }
        }

        public FrameworkElement PlacementTarget
        {
            get { return (FrameworkElement)GetValue(PlacementTargetProperty); }
            set { SetValue(PlacementTargetProperty, value); }
        }
    }
}
